package hall

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

func NopDecoder[Req any](_ context.Context, _ io.Reader, _ *Req) error {
	return nil
}

func StringEncoder[Resp any](_ context.Context, v Resp, writer io.Writer) error {

	_, err := fmt.Fprintf(writer, "%v", v)
	if err != nil {
		return fmt.Errorf("string encoder failed: %w", err)
	}
	return nil

}

func JsonDecoder[Req any](_ context.Context, reader io.Reader, v *Req) error {

	if reader == nil || reader == http.NoBody {
		return nil
	}

	decoder := json.NewDecoder(reader)
	err := decoder.Decode(v)
	if err != nil {
		return fmt.Errorf("error decoding json request: %w", err)
	}
	return nil

}

func JsonEncoder[Resp any](_ context.Context, v Resp, writer io.Writer) error {

	encoder := json.NewEncoder(writer)
	err := encoder.Encode(&v)
	if err != nil {
		return fmt.Errorf("error encoding json request: %w", err)
	}
	return nil

}

// AutoDecoder decode request according to the content-type header
func AutoDecoder[Req any](ctx context.Context, reader io.Reader, v *Req) error {

	request, ok := ctx.Value(RequestKey).(*http.Request)
	if !ok {
		return nil
	}

	contentType := request.Header.Get("Content-Type")
	switch contentType {
	case "application/x-www-form-urlencoded":

		err := request.ParseForm()
		if err != nil {
			return fmt.Errorf("parse form: %w", err)
		}

		//check if Req can convert to map string and interface
		if m, ok := (any(v)).(map[string]interface{}); ok {
			for key := range request.Form {
				value := request.FormValue(key)
				//auto convert number just like json
				floatValue, err := strconv.ParseFloat(value, 64)
				if err == nil {
					m[key] = floatValue
				} else {
					m[key] = value
				}
			}
		}

		//check if Req can convert to map string and string
		if m, ok := (any(v)).(map[string]string); ok {
			for key := range request.Form {
				m[key] = request.FormValue(key)
			}
		}

		//else do nothing
		return nil

	case "application/json":
		return JsonDecoder[Req](ctx, reader, v)
	default:
		return NopDecoder[Req](ctx, reader, v)
	}

}

// AutoEncoder encode response to writer according to the accept and content-type header
func AutoEncoder[Resp any](ctx context.Context, v Resp, writer io.Writer) error {

	request, ok := ctx.Value(RequestKey).(*http.Request)
	if !ok {
		return StringEncoder(ctx, v, writer)
	}

	accept := request.Header.Get("Accept")
	contentType := request.Header.Get("Content-Type")
	switch {
	case contentType == "application/json" || strings.Contains(accept, "application/json"):
		return JsonEncoder[Resp](ctx, v, writer)
	default:
		switch reflect.TypeOf(v).Kind() {
		case reflect.Slice, reflect.Array, reflect.Map, reflect.Struct, reflect.Pointer:
			return JsonEncoder[Resp](ctx, v, writer)
		case reflect.Func, reflect.Interface, reflect.Chan:
			return nil
		default:
			return StringEncoder[Resp](ctx, v, writer)
		}
	}

}
